using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;

namespace FileUtility.FTP.FtpSupport
{
	/// <summary>
	/// Summary description for FtpConnection.
	/// </summary>
	public class FtpConnection : IDisposable
	{
		private IntPtr hInternet;
		private IntPtr hConnect;

		#region Constructors
		public FtpConnection()
		{
			hInternet = Open();
		}

		public FtpConnection(string serverOrIp) : this(serverOrIp, null, null)
		{
		}

		public FtpConnection(string serverOrIp, string userName, string password)
		{
			//Use a temp to remain exception-safe
			IntPtr hTempInternet = Open();
			hConnect = InternalConnect(hTempInternet, serverOrIp, userName, password);
			//If we made it here no exception occurred
			hInternet = hTempInternet;
		}

		public FtpConnection(string serverOrIp, string userName, string password, int nPort)
		{
			//Use a temp to remain exception-safe
			IntPtr hTempInternet = Open();
			hConnect = InternalConnect(hTempInternet, serverOrIp, userName, password, nPort);
			//If we made it here no exception occurred
			hInternet = hTempInternet;
		}
		#endregion Constructors

		public void Connect(string serverOrIp)
		{
			Connect(serverOrIp, null, null);
		}

		public void Connect(string serverOrIp, string userName, string password)
		{
			SafeCloseHandle(ref hConnect);
			hConnect = InternalConnect(hInternet, serverOrIp, userName, password);
		}
		public bool CreateDirectory(string directory)
		{
			return NativeMethods.FtpCreateDirectory(hConnect,directory);
		}

		private static IntPtr InternalConnect(IntPtr hInt, string serverOrIp, string userName, string password)
		{
			IntPtr hTempConnect = NativeMethods.InternetConnect(hInt, serverOrIp, InternetPort.DefaultFtpPort, userName, password, InternetService.Ftp, 0, IntPtr.Zero);
			FtpException.THROWONNULL(hTempConnect);
			return hTempConnect;
		}

		private static IntPtr InternalConnect(IntPtr hInt, string serverOrIp, string userName, string password, int nPort)
		{
			IntPtr hTempConnect = NativeMethods.InternetConnect(hInt, serverOrIp, (InternetPort)nPort, userName, password, InternetService.Ftp, 0, IntPtr.Zero);
			FtpException.THROWONNULL(hTempConnect);
			return hTempConnect;
		}

		public FtpStream OpenFile(string fileName, GenericRights rights)
		{
			return OpenFile(fileName, rights, FtpTransferType.Binary);
		}

		public FtpStream OpenFile(string fileName, GenericRights rights, FtpTransferType flags)
		{
			//TODO: create an FtpFileStream class
			IntPtr hFile = NativeMethods.FtpOpenFile(hConnect, fileName, rights, flags, IntPtr.Zero);
			FtpException.THROWONNULL(hFile);
			return new FtpStream(hFile, rights);
		}

		public void GetFile(string remoteFile, string localFile, bool failIfExists, FileAttributes attributes)
		{
			GetFile(remoteFile, localFile, failIfExists, attributes, FtpTransferType.Binary);
		}

		public void GetFile(string remoteFile, string localFile, bool failIfExists, FileAttributes attributes, FtpTransferType transfer)
		{
			bool rc = NativeMethods.FtpGetFile(hConnect, remoteFile, localFile, failIfExists, attributes, transfer, IntPtr.Zero);
			FtpException.THROWONFALSE(rc);
		}

		public void PutFile(string localFile, string newRemoteFile)
		{
			PutFile(localFile, newRemoteFile, FtpTransferType.Binary);
		}

		public void PutFile(string localFile, string newRemoteFile, FtpTransferType transfer)
		{
			bool rc = NativeMethods.FtpPutFile(hConnect, localFile, newRemoteFile, transfer, IntPtr.Zero);
			FtpException.THROWONFALSE(rc);
		}

		public void PutStream(System.IO.Stream stream, string newRemoteFile)
		{
			PutStream(stream, newRemoteFile, FtpTransferType.Binary);
		}

		public void PutStream(System.IO.Stream stream, string newRemoteFile, FtpTransferType transfer)
		{
			long originalPosition = 0;
			if(stream.CanSeek)
			{
				originalPosition = stream.Position;
			}

			using(FtpStream ftpStream = OpenFile(newRemoteFile, GenericRights.Write, transfer))
			{
				byte[] buffer = new byte[1024];
				int bytesRead = stream.Read(buffer, 0, buffer.Length);
				while(bytesRead > 0)
				{
					ftpStream.Write(buffer, 0, bytesRead);
					bytesRead = stream.Read(buffer, 0, buffer.Length);
				}
			}

			if(stream.CanSeek)
			{
				stream.Position = originalPosition;
			}
		}

		public void RenameFile(string existing, string newName)
		{
			bool rc = NativeMethods.FtpRenameFile(hConnect, existing, newName);
			FtpException.THROWONFALSE(rc);
		}

		public string GetCurrentDirectory()
		{
			string dir;
			bool rc = NativeMethods.FtpGetCurrentDirectory(hConnect, out dir);
			FtpException.THROWONFALSE(rc);
			return dir;
		}

		public void SetCurrentDirectory(string directory)
		{
			bool rc = NativeMethods.FtpSetCurrentDirectory(hConnect, directory);
			FtpException.THROWONFALSE(rc);
		}

		public void DeleteFile(string fileName)
		{
			bool rc = NativeMethods.FtpDeleteFile(hConnect, fileName);
			FtpException.THROWONFALSE(rc);
		}

		public void RemoveDirectory(string directory)
		{
			bool rc = NativeMethods.FtpRemoveDirectory(hConnect, directory);
			FtpException.THROWONFALSE(rc);
		}

		public Win32FindData[] FindFiles()
		{
			return FindFiles(null);
		}

		public Win32FindData[] FindFiles(string filter)
		{
			Win32FindData fileData;
			IntPtr hFind = NativeMethods.FtpFindFirstFile(hConnect, filter, out fileData, 0, IntPtr.Zero);
			FtpException.THROWONNULL(hFind);
			try
			{
				ArrayList results = new ArrayList();
				bool bContinue;
				do
				{
					results.Add(fileData);
					bContinue = NativeMethods.InternetFindNextFile(hFind, out fileData);
				}while(bContinue);

				return (Win32FindData[])results.ToArray(typeof(Win32FindData));
			} 
			finally
			{
				NativeMethods.InternetCloseHandle(hFind);
			}
		}
		public Win32FindData[] GetList(FileAttributes attrbt,string filter)
		{
			Win32FindData[] fileData=FindFiles(filter);
			ArrayList results = new ArrayList();
			if(fileData.Length>0)
			{
				for(int i=0;i<fileData.Length;i++)
				{
					
						if(fileData[i].fileAttributes==attrbt)
						{
							results.Add(fileData[i]);
						}
						
						
				}
				
				return (Win32FindData[])results.ToArray(typeof(Win32FindData));
			}
			else
			{
				return fileData;
			}
		}

		~FtpConnection()
		{
			((IDisposable)this).Dispose();
		}

		public void Close()
		{
			((IDisposable)this).Dispose();
		}

		void IDisposable.Dispose()
		{
			SafeCloseHandle(ref hConnect);
			SafeCloseHandle(ref hInternet);
			GC.SuppressFinalize(this);
		}

		#region Private members


		private static IntPtr Open()
		{
			IntPtr hTemp = NativeMethods.InternetOpen("FtpConnection", InternetOpenType.Direct, null, null, 0);
			FtpException.THROWONNULL(hTemp);
			return hTemp;
		}

		internal static void SafeCloseHandle(ref IntPtr handle)
		{
			if(handle != IntPtr.Zero)
			{
				NativeMethods.InternetCloseHandle(handle);
				handle = IntPtr.Zero;
			}
		}
		#endregion Private members
	}
}
